package ro.myApp.commons.service;

import java.util.List;
import java.util.Map;

import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.NonUniqueResultException;
import javax.persistence.Query;

import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projections;

import ro.myApp.commons.basic.PersistentEntity;

public abstract class EntityAccessServiceImpl<E extends PersistentEntity> 
	implements EntityAccessService<E> {

	protected Class<E> clazz;
	
	protected abstract EntityManager getEm();
	
	protected Session getSession (){
		return ((Session)getEm().getDelegate());
	}
	
	/**
	 * instantiate clazz here
	 */
	protected abstract void init ();
	
	@Override
	public E findById(Long id) {
		if ((clazz == null) || (id == null) || (id < 1))
			throw new IllegalArgumentException("Valid class and id parameters are mandatory");
		return getEm().find(clazz, id);
	}

	@Override
	public E findUniqueFiltered(Criterion...criteria) throws NonUniqueResultException {
		List<E> result = findFiltered(criteria);
		if (null==result || result.isEmpty())
			throw new NoResultException();
		if (result.size()>1)
			throw new NonUniqueResultException();
		return result.get(0);
	}

	@Override
	public List<E> findAll() {
		return findFiltered();
	}
	
	@Override
	public List<E> findAll(int maxResults) {
		return findFiltered(maxResults);
	}
	
	public List<E> findFiltered(Criterion...criteria) {
		return findFiltered(0, criteria);
		
	}
	
	@SuppressWarnings("unchecked")
	@Override
	public List<E> findFiltered(int maxResults, Criterion...criteria) {
		return findFiltered(null, false, maxResults, criteria);
	}
	
	@Override
	public List<E> findFiltered(String orderBy, boolean direction, int maxResults,
			Criterion[] criteria) {
		if (clazz == null) {
			throw new IllegalArgumentException("Class is mandatory");
		}
		Criteria crit = getSession().createCriteria(clazz);
		if (null!=criteria){
			for (Criterion criterion : criteria) {
				crit.add(criterion);
			}
		}
		if (maxResults>0)
			crit.setMaxResults(maxResults);
		if (null!=orderBy)
			if (direction)
				crit.addOrder(Order.asc(orderBy));
			else
				crit.addOrder(Order.desc(orderBy));
		return crit.list();
	}
	
	@Override
	public int countFiltered(Criterion...criteria) {
		if (clazz == null) {
			throw new IllegalArgumentException("Class is mandatory");
		}
		Criteria crit = getSession().createCriteria(clazz);
		if (null!=criteria){
			for (Criterion criterion : criteria) {
				crit.add(criterion);
			}
		}
		crit.setProjection(Projections.countDistinct("id"));
		return (Integer) crit.uniqueResult();
	}
	
	/**
	 * method meant to me used only by classes extending this one
	 * @param queryName - name of the query that has to be found 
	 * 			in the appropriate *_orm.xml file of current module
	 * @param params - the Map consists in association between property names and values
	 * @return list of elements matching the search criteria or empty list if no 
	 * 			elements were found
	 */
	@SuppressWarnings("unchecked")
	protected <T> List<T> executeNamedQuery (String queryName, Map<String, Object> params, Class<T> expectedResultType){
		if (null==queryName || queryName.isEmpty())
			throw new IllegalArgumentException("query name parameter is mandatory");
		Query query = getEm().createNamedQuery(queryName);
		return (List<T>)query.getResultList();
	}
}
